#include "flipc/gvl/support/cstdint.hpp"
#include "flipc/gvl/support/platform.hpp"
#include "flipc/gvl/resman/shared.h"
#include <stddef.h>
#include <stdlib.h>

/*

instr ::= cond uncond_instr

uncond_instr ::=
	add x, y, z // x = y + z
	addf x, y, z // x = y + z
	mov x, y // x = y
	citof x, y // x = (double)y
	cftoi x, y // x = (int)round(y)

	
cond ::=
	if x < y:
	if x == y:
	if double x < y:
	
operand ::=
	reg
	[ea]


ea ::=
	reg
	reg + N
	reg*N + N2
	reg*N + reg + N2




*/

typedef struct gvm_buffer_
{
	gvl_shared base;
	uint64_t code[1];
} gvm_buffer;

#define GVM_BUFFER_SIZE(len) (sizeof(gvm_buffer)+len*sizeof(uint64_t)-sizeof(uint64_t))

gvm_buffer* gvm_new_buffer(unsigned len)
{
	gvm_buffer* self = malloc(GVM_BUFFER_SIZE(len));
	gvl_shared_init(&self->base, gvl_malloc_freer);
	return self;
}

typedef struct gvm_state_
{
	uint64_t regs[64];
	uint64_t* ip;
	gvm_buffer* code;
	uint64_t* mem;
	int cycles;
} gvm_state;

enum
{
	GVM_ADD = 0,
	GVM_LEA,
	GVM_LT
};

enum
{
	GVM_EA_REG = 0,
	GVM_EA_IND_REG,
	GVM_EA_IND_REG_OFFS,
	GVM_EA_IND_REG_SCALE_OFFS,
	GVM_EA_IND_REG_SCALE_REG_OFFS
};

/* ea:

13..15: ea type
6..12: reg1
0..5: reg2
*/

GVL_INLINE uint32_t ea_reg1(uint32_t ea_data)
{
	return (ea_data >> 6) & 63;
}

GVL_INLINE uint32_t ea_reg2(uint32_t ea_data)
{
	return ea_data & 63;
}

static uint64_t resolve_mem_ea(gvm_state* state, uint32_t ea_data, uint64_t** code)
{
	switch(ea_data >> 13)
	{
		case GVM_EA_IND_REG:
			return state->regs[ea_reg1(ea_data)];
			
		case GVM_EA_IND_REG_OFFS:
		{
			uint64_t offset = *(*code)++;
			return state->regs[ea_reg1(ea_data)] + offset;
		}
		
		case GVM_EA_IND_REG_SCALE_OFFS:
		{
			uint64_t offset = *(*code)++;
			uint64_t scale = *(*code)++;
			return state->regs[ea_reg1(ea_data)]*scale + offset;
		}
		
		case GVM_EA_IND_REG_SCALE_REG_OFFS:
		{
			uint64_t offset = *(*code)++;
			uint64_t scale = *(*code)++;
			uint64_t reg1 = state->regs[ea_reg1(ea_data)];
			uint64_t reg2 = state->regs[ea_reg2(ea_data)];
			return reg1*scale + reg2 + offset;
		}
	}
	
	return 0;
}

static uint64_t* resolve_ea(gvm_state* state, uint32_t ea_data, uint64_t** code)
{
	switch(ea_data >> 13)
	{
		case GVM_EA_REG:
			return &state->regs[ea_reg1(ea_data)];
		
		default:
			return &state->mem[resolve_mem_ea(state, ea_data, code)];
	}
	return NULL;
}

static void skip_ea(uint32_t ea_data, uint64_t** code)
{
	switch(ea_data >> 13)
	{
		case GVM_EA_REG:
			break;
		
		case GVM_EA_IND_REG_OFFS:
		{
			++*code;
			break;
		}
		
		case GVM_EA_IND_REG_SCALE_OFFS:
		case GVM_EA_IND_REG_SCALE_REG_OFFS:
		{
			(*code) += 2;
			break;
		}
	}
}

#define SAVE_STATE do { \
	state->cycles = cycles; \
	state->ip = ip; \
} while(0)

#define OPERAND1(w) ((w)&0xffff)
#define OPERAND2(w) (((w)>>16)&0xffff)
#define OPERAND3(w) (((w)>>32)&0xffff)
#define OPERATOR(w) ((w)>>48)

void skip(uint64_t** ip)
{
	uint64_t w = *(*ip)++;
	switch(OPERATOR(w))
	{
		case GVM_ADD:
		{
			skip_ea(OPERAND1(w), ip);
			skip_ea(OPERAND2(w), ip);
			skip_ea(OPERAND3(w), ip);
			break;
		}
		
		case GVM_LEA:
		{
			skip_ea(OPERAND1(w), ip);
			skip_ea(OPERAND2(w), ip);
			break;
		}	
	}
}

void gvm_interp(gvm_state* state)
{
	uint64_t* ip = state->ip;
	
	int cycles = state->cycles;
	
	while(cycles > 0)
	{
		uint64_t w = *ip++;
		switch(OPERATOR(w))
		{
			case GVM_ADD:
			{
				uint64_t* x = resolve_ea(state, w & 0xffff, &ip);
				uint64_t* y = resolve_ea(state, (w >> 16) & 0xffff, &ip);
				uint64_t* z = resolve_ea(state, (w >> 32) & 0xffff, &ip);
				
				*x = *y + *z;
				--cycles;
				break;
			}
			
			case GVM_LEA:
			{
				uint64_t* x = resolve_ea(state, OPERAND1(w), &ip);
				uint64_t  y = resolve_mem_ea(state, OPERAND2(w), &ip);
				
				*x = y;
				--cycles;
				break;
			}
			
			case GVM_LT:
			{
				uint64_t* x = resolve_ea(state, w & 0xffff, &ip);
				uint64_t* y = resolve_ea(state, (w >> 16) & 0xffff, &ip);
				
				if(*x >= *y)
					skip(&ip);
				--cycles;
				break;
			}
		}
	}
	
	SAVE_STATE;
}